package com.stars.easyms.feign.response;

import com.alibaba.fastjson.JSON;
import com.stars.easyms.base.alarm.EasyMsAlarmAssistor;
import com.stars.easyms.base.constant.HttpHeaderConstants;
import com.stars.easyms.base.bean.EasyMsResponseEntity;
import com.stars.easyms.base.util.*;
import com.stars.easyms.feign.exception.BusinessFeignException;
import com.stars.easyms.feign.plugin.EasyMsFeignPluginRegister;
import com.stars.easyms.feign.properties.EasyMsFeignProperties;
import feign.Request;
import feign.Response;
import feign.codec.Decoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.core.io.InputStreamSource;
import org.springframework.http.HttpEntity;
import org.springframework.lang.Nullable;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Map;

/**
 * 为feign增加response解析
 *
 * @author guoguifang
 * @date 2020-08-17 12:34
 * @since 1.6.1
 */
@Slf4j
public class EasyMsFeignDecoder extends ResponseEntityDecoder {

    private final Decoder easyMsDecoder;

    private final ResponseEntityDecoder responseEntityDecoder;

    private final EasyMsFeignProperties easyMsFeignProperties;

    public EasyMsFeignDecoder(Decoder easyMsDecoder, Decoder decoder) {
        super(decoder);
        this.easyMsDecoder = easyMsDecoder;
        this.responseEntityDecoder = new ResponseEntityDecoder(decoder);
        this.easyMsFeignProperties = ApplicationContextHolder.getApplicationContext().getBean(EasyMsFeignProperties.class);
    }

    @Override
    public Object decode(final Response response, Type type) throws IOException {

        // 获取响应头信息
        Request request = response.request();
        Map<String, Collection<String>> requestHeaders = request.headers();
        Map<String, Collection<String>> responseHeaders = response.headers();
        String requestId = TraceUtil.withUnknown(getValueFromHeader(requestHeaders, HttpHeaderConstants.HEADER_KEY_REQUEST_ID));
        String asyncId = getValueFromHeader(requestHeaders, HttpHeaderConstants.HEADER_KEY_ASYNC_ID);
        String requestSys = TraceUtil.withUnknown(getValueFromHeader(requestHeaders, HttpHeaderConstants.HEADER_KEY_REQUEST_SYS));
        String responseSys = getValueFromHeader(responseHeaders, requestHeaders, HttpHeaderConstants.HEADER_KEY_RESPONSE_SYS);
        String requestPath = getValueFromHeader(requestHeaders, HttpHeaderConstants.HEADER_KEY_REQUEST_PATH);
        if (requestPath == null) {
            requestPath = response.request().url();
        }

        // 判断响应type是否是HttpEntity<T>或者接收的是一个InputStreamSource对象则直接使用responseEntityDecoder解析
        Type actualTypeArgumentType = getHttpEntityArgumentType(type);
        if (HttpEntity.class == actualTypeArgumentType || (actualTypeArgumentType instanceof Class
                && InputStreamSource.class.isAssignableFrom((Class<?>) actualTypeArgumentType))) {

            // 记录请求日志
            if (!EasyMsFeignPluginRegister.isIgnoredTraceUrl(requestPath)) {
                log.info("[调用服务-响应]-[请求地址: {}]-[请求系统: {}]-[服务系统: {}]-[请求ID: {}]{}-[接收响应时间: {}].",
                        requestPath, requestSys, responseSys, requestId, TraceUtil.getAsyncIdTrace(asyncId), DateTimeUtil.now());
            }

            return responseEntityDecoder.decode(response, type);
        }

        // 如果type是HttpEntity<T>类型的则实际转换类型为T
        if (actualTypeArgumentType != null) {
            type = actualTypeArgumentType;
        }

        // 判断是否是easy-ms的响应体，如果不是easy-ms的响应体则直接使用流的方式读取，如果解析错误则使用父类的解析
        boolean isEasyMsResponse = getValueFromHeader(responseHeaders, HttpHeaderConstants.HEADER_KEY_EASY_MS) != null;
        if (!isEasyMsResponse) {
            return decodeNonEasyMsResponse(response, type, requestPath, requestSys, responseSys, requestId, asyncId);
        }

        // 把response解析成EasyMsResponseEntity类型
        Object object;
        try {
            if (EasyMsResponseEntityUtil.isEasyMsResponseEntityType(type)) {
                object = easyMsDecoder.decode(response, type);
            } else {
                object = easyMsDecoder.decode(response, EasyMsResponseEntityUtil.getEasyMsResponseEntityParameterizedType(type));
            }
        } catch (Exception e) {

            // 记录错误信息并告警
            log.error("解析响应数据失败", e);
            EasyMsAlarmAssistor.sendExceptionAlarmMessage(e);

            // 如果解析EasyMsResponseEntityType类型失败则使用非easy-ms模式解析
            return decodeNonEasyMsResponse(response, type, requestPath, requestSys, responseSys, requestId, asyncId);
        }

        // 记录请求日志
        if (!EasyMsFeignPluginRegister.isIgnoredTraceUrl(requestPath)) {
            Object recordObj = object instanceof EasyMsResponseEntity ? object : JsonUtil.toJSONString(object);
            log.info("[调用服务-响应]-[请求地址: {}]-[请求系统: {}]-[服务系统: {}]-[请求ID: {}]{}-[响应时间: {}]-[接收响应时间: {}]{}.",
                    requestPath, requestSys, responseSys, requestId, TraceUtil.getAsyncIdTrace(asyncId),
                    TraceUtil.withUnknown(getValueFromHeader(responseHeaders, HttpHeaderConstants.HEADER_KEY_RESPONSE_TIME)),
                    DateTimeUtil.now(), easyMsFeignProperties.isLogResponse() ? "-响应数据: " + recordObj : "");
        }

        // 如果返回的Type不是EasyMsResponseEntity类型的，则判断是否成功，如果返回的信息不是正确信息，则抛出异常
        if (!EasyMsResponseEntityUtil.isEasyMsResponseEntityType(type) && object instanceof EasyMsResponseEntity) {
            EasyMsResponseEntity<?> responseEntity = (EasyMsResponseEntity) object;
            if (!responseEntity.isSuccess()) {
                throw new BusinessFeignException(response.status(), responseEntity.getErrorDesc(), response.request(),
                        responseEntity.getRetCode(), responseEntity.getRetMsg(), responseEntity.getErrorDesc());
            }
            if (responseEntity.getBody() != null) {
                return responseEntity.getBody();
            }
            return type == Boolean.class || type == boolean.class ? Boolean.TRUE : null;
        }
        return object;
    }

    private Object decodeNonEasyMsResponse(final Response response, Type type, String requestPath, String requestSys,
                                           String responseSys, String requestId, String asyncId) throws IOException {
        try (InputStream inStream = response.body().asInputStream()) {
            // 从响应流中获取响应数据
            StringBuilder sb = new StringBuilder();
            byte[] buf = new byte[1024];
            for (int n; (n = inStream.read(buf)) != -1; ) {
                sb.append(new String(buf, 0, n, StandardCharsets.UTF_8));
            }
            String result = sb.toString();

            // 记录响应日志信息
            if (!EasyMsFeignPluginRegister.isIgnoredTraceUrl(requestPath)) {
                log.info("[调用服务-响应]-[请求地址: {}]-[请求系统: {}]-[服务系统: {}]-[请求ID: {}]{}-[接收响应时间: {}]{}.",
                        requestPath, requestSys, responseSys, requestId,  TraceUtil.getAsyncIdTrace(asyncId), DateTimeUtil.now(),
                        easyMsFeignProperties.isLogResponse() ? "-响应数据: " + result : "");
            }

            // 如果返回类型为String则直接返回，如果不是String则使用fastJson转换成对应类型的对象返回
            if (type == String.class) {
                return result;
            }
            return JSON.parseObject(result, type);
        } catch (Exception e) {

            // 记录响应日志信息
            if (!EasyMsFeignPluginRegister.isIgnoredTraceUrl(requestPath)) {
                log.error("[调用服务-响应]-[请求地址: {}]-[请求系统: {}]-[服务系统: {}]-[请求ID: {}]{}-[接收响应时间: {}].",
                        requestPath, requestSys, responseSys, requestId, TraceUtil.getAsyncIdTrace(asyncId), DateTimeUtil.now(), e);
            }

            // 如果有异常则抛出异常
            throw e;
        }
    }

    private String getValueFromHeader(Map<String, Collection<String>> responseHeaders,
                                      Map<String, Collection<String>> requestHeaders, String key) {
        String value = getValueFromHeader(responseHeaders, key);
        if (value == null) {
            value = getValueFromHeader(requestHeaders, key);
        }
        return TraceUtil.withUnknown(value);
    }

    @Nullable
    private String getValueFromHeader(Map<String, Collection<String>> headers, String key) {
        Collection<String> collection = headers.get(key);
        if (collection != null && !collection.isEmpty()) {
            for (String value : collection) {
                if (value != null) {
                    return value;
                }
            }
        }
        return null;
    }

    private Type getHttpEntityArgumentType(Type type) {
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            Type rawType = parameterizedType.getRawType();
            if (rawType instanceof Class) {
                Class c = (Class) rawType;
                if (HttpEntity.class.isAssignableFrom(c)) {
                    Type[] actualTypeArgumentTypes = parameterizedType.getActualTypeArguments();
                    if (actualTypeArgumentTypes.length == 1) {
                        return actualTypeArgumentTypes[0];
                    } else {
                        return HttpEntity.class;
                    }
                }
            }
        }
        return null;
    }

}
